// Copyright (C) 2023 即时通讯网(52im.net) & Jack Jiang.
// The RainbowChat Project. All rights reserved.
// 
// 【本产品为著作权产品，合法授权后请放心使用，禁止外传！】
// 【本次授权给：<北京小羊驼科技有限公司>，授权编号：<NT231212144350>，代码指纹：<A.702363430.550>，技术对接人微信：<ID: wxid_wbyootvkdcgj22>】
// 【授权寄送：<收件：苏先生、地址：北京市大兴区北京密码西区6号楼1单元301、电话：18613885610、邮箱：bd@huiyuanxiang-inc.com>】
// 
// 【本系列产品在国家版权局的著作权登记信息如下】：
// 1）国家版权局登记名(简称)和权证号：RainbowChat    （证书号：软著登字第1220494号、登记号：2016SR041877）
// 2）国家版权局登记名(简称)和权证号：RainbowChat-Web（证书号：软著登字第3743440号、登记号：2019SR0322683）
// 3）国家版权局登记名(简称)和权证号：RainbowAV      （证书号：软著登字第2262004号、登记号：2017SR676720）
// 4）国家版权局登记名(简称)和权证号：MobileIMSDK-Web（证书号：软著登字第2262073号、登记号：2017SR676789）
// 5）国家版权局登记名(简称)和权证号：MobileIMSDK    （证书号：软著登字第1220581号、登记号：2016SR041964）
// * 著作权所有人：江顺/苏州网际时代信息科技有限公司
// 
// 【违法或违规使用投诉和举报方式】：
// 联系邮件：jack.jiang@52im.net
// 联系微信：hellojackjiang
// 联系QQ号：413980957
// 授权说明：http://www.52im.net/thread-1115-1-1.html
// 官方社区：http://www.52im.net
package com.x52im.rbchat.logic.chat_root.sendshortvideo;

import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;

import com.alibaba.fastjson.JSONObject;
import com.eva.android.widget.DataLoadingAsyncTask;
import com.eva.android.widget.WidgetUtils;
import com.eva.epc.common.util.CommonUtils;
import com.eva.framework.dto.DataFromServer;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.MyApplication;
import com.x52im.rainbowchat.http.file.dto.BigFileType;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rbchat.logic.chat_root.model.Message;
import com.x52im.rbchat.network.http.HttpFileUploadHelper;
import com.x52im.rbchat.network.http.HttpRestHelper;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadManager;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadTask;
import com.x52im.rbchat.network.http.bigfile.BigFileUploadTaskListener;

import java.io.File;
import java.util.HashMap;
import java.util.Observer;

/**
 * 发送短视频消息的各种实用方法辅助类.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @since 4.4
 */
public class SendShortVideoHelper
{
    public final static String TAG = SendShortVideoHelper.class.getSimpleName();

    /**
     * 实现短视频消息中的视频文件数据数据上传（支持断点续传逻辑），以及上传完成后的处理等全流程。
     *
     * @param context
     * @param videoFileName 要上传的文件名
     * @param videoFilePath 要上传文件的完整路径
     * @param videoFileMd5 文件的md5码
     * @param cme 对应聊天界面中一条大文件消息的数据模型
     * @param observerForFileUploadProcessOK 观察者：用于上传完成时通知本方法的调用者来做余下的事（把这个观察者当回调来理解就好了）
     */
    public static void processShortVideoUpload(final Context context, final String videoFileName
            , final String videoFilePath, final String videoFileMd5, final Message cme
            , final Observer observerForFileUploadProcessOK)
    {
        /* =====【1】上传状态监听器（用于处理文件上传过程中的各种状态切换逻辑）===== */
        final ShortVideoUploadTaskListenerForChat utl = new ShortVideoUploadTaskListenerForChat(context, cme) {

            // 视频文件上传成功后，会通知本方法，本方法中可以继续视频首页图片的上传处理逻辑
            @Override
            protected void notificateFileUploaedSucessObserver() {
                boolean refreshCME = true;

                try {
                    //** 开始处理视频首帧预览图的上传逻辑
                        // 视频首帧预览图的文件名（服务端保存的名）
                        final String imgtToServerName = ReceivedShortVideoHelper.constructShortVideoThumbName_toServer(videoFileMd5);
                        // 视频首帧预览图的文件名（本地保存的名）
                        final String imgtLocalSavedName = ReceivedShortVideoHelper.constructShortVideoThumbName_localSaved(videoFileName);
                        // 视频首帧预览图将要保存的完整路径
                        final File imgFile = new File(ReceivedShortVideoHelper.getReceivedShortVideoPreviewSavedDirHasSlash()+imgtLocalSavedName);

                        // 首帧预览图已在之前准备好了（可以进入上传逻辑）
                        if(imgFile.exists()) {
                            Log.i(TAG, "【短视频上传-上传首帧预览图】首帧预览图已存在，马上进入上传逻辑....（文件位置："+imgFile.getAbsolutePath()+"）");

                            // 本地用户信息
                            final RosterElementEntity localUser = MyApplication.getInstance2().getIMClientManager().getLocalUserInfo();
                            if(localUser != null) {
                                refreshCME = false;

                                // 异步上传到服务端
                                new DataLoadingAsyncTask<Object, Integer, Boolean>(MyApplication.getInstance2(), false){

                                    @Override
                                    protected Boolean doInBackground(Object... objects) {
                                        Boolean result = Boolean.FALSE;

                                        // 将本地用户的uid作为参数传递到服务端
                                        HashMap<String, String> requestProperties = new HashMap<String, String>();
                                        // 此参数名注意要与服务端保持一致哦
                                        requestProperties.put("user_uid", localUser.getUser_uid());
                                        requestProperties.put("file_name", imgtToServerName); // 此文件名为服务端磁盘保存名（注意与服务端逻辑保持一致）

                                        // 开始上传预览图
                                        result = Boolean.valueOf(HttpFileUploadHelper.uploadFile(
                                                imgFile.getAbsolutePath()
                                                , imgtToServerName
                                                , Const.SHORTVIDEO_THUMB_UPLOADER_CONTROLLER_URL_ROOT
                                                , requestProperties));

                                        return result;
                                    }

                                    @Override
                                    protected void onPostExecuteImpl(Object _result) {
                                        if(_result != null) {
                                            Boolean result = (Boolean)_result;

                                            // 上传成功
                                            if(result) {
                                                Log.i(TAG, "【短视频上传-上传首帧预览图】首帧文件上传成功【OK】（"+imgFile.getAbsolutePath()+"）");
                                            } else {
                                                Log.w(TAG, "【短视频上传-上传首帧预览图】首帧文件上传失败【NO】（"+imgFile.getAbsolutePath()+"）");
                                            }

                                            // 不管视频首帧处理有没有成功，都设置聊天列表中的数据单元对象中的文件上传状态为OK
                                            // (因为即使视频预览图上传失败，但不影响用户查看视频啊，必竟视频已经正常上传完成了嘛)
                                            entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processOk);
                                            // 通知观察者，尝试刷新UI界面上的上传进度显示
                                            notificateStatusChangedObserver(videoFileName, videoFileMd5, videoFilePath);
                                        }
                                    }
                                }.execute();
                            } else {
                                Log.w(TAG, "【短视频上传-上传首帧预览图】localUser==null，本次上传没有继续【NO】!");
                            }
                        } else {
                            Log.w(TAG, "【短视频上传-上传首帧预览图】首帧预览图文件不存，本次上传没有继续【NO】!");
                        }
                } catch (Exception e) {
                    Log.w(TAG, "【短视频上传-处理首帧预览图】上传首帧逻辑时出错了【NO】，原因："+e.getMessage(), e);
                } finally {
                    if(refreshCME) {
                        // 不管视频首帧处理有没有成功，都设置聊天列表中的数据单元对象中的文件上传状态为OK
                        // (因为即使视频预览图上传失败，但不影响用户查看视频啊，必竟视频已经正常上传完成了嘛)
                        entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processOk);
                        // 通知观察者，尝试刷新UI界面上的上传进度显示
                        notificateStatusChangedObserver(videoFileName, videoFileMd5, videoFilePath);
                    }
                }

                //** 文件上传已完成，通知观察者可以做余下的事了
                if(observerForFileUploadProcessOK != null)
                    observerForFileUploadProcessOK.update(null, null);
            }
        };


        /* =====【2】开始处理大文件上传逻辑 ===== */
        final BigFileUploadManager um = MyApplication.getInstance(context).getIMClientManager().getBigFileUploadManager();
        if(um != null)
        {
            // 检查文件是否正在上传中（确保相同md5的任务只有一个在上传中）
            if(um.isUploading(videoFileMd5))
            {
                Log.w(TAG, "【短视频上传】要上传大文件："+videoFilePath+"， 已存在相同的上传任务，本次任务没有继续！");
                utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
                return;
            }

            // 检查参数的合法性
            if (videoFilePath == null || videoFileName == null || videoFileMd5 == null)
            {
                Log.w(TAG, "【短视频上传】相关参数不能为空，本次上传取消（videoFilePath="+videoFilePath
                        +", videoFileName="+videoFileName+", videoFileMd5="+videoFileMd5+"）!");
                utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
                return;
            }

            // 本地用户信息
            final RosterElementEntity localUser = MyApplication.getInstance(context)
                    .getIMClientManager().getLocalUserInfo();
            if (localUser != null)
            {
                //** 首先向服务端提交大文件信息查询请求（看看是否需要上传，或者要从第几块开始上传）
                new AsyncTask<String, Integer, DataFromServer>() {// 网络请示运行在异步线程中提升体验
                    @Override
                    protected DataFromServer doInBackground(String... strings)
                    {
                        return HttpRestHelper.queryBigFileInfoFromServer(videoFileMd5, localUser.getUser_uid(), BigFileType.FILE_TYPE_SHORT_VIDEO);
                    }

                    protected void onPostExecute(DataFromServer dfs)
                    {
                        // 本次开始上传的分块索引起始块
                        int startChunck = 1;

                        if (dfs == null || !dfs.isSuccess())
                        {
                            Log.w(TAG, "【短视频上传】从服务端查询该文件的信息失败了，原因是：" + dfs.getReturnValue()
                                    + "，本次任务没有继续！（videoFilePath=" + videoFilePath + ", videoFileName=" + videoFileName + ", videoFileMd5=" + videoFileMd5 + "）");
                            utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
                            return;
                        }
                        else
                        {
                            if (dfs.getReturnValue() != null)
                            {
                                // 返回结果详见http文档中“【接口1015-23-7】”的详细说明
                                JSONObject ret = HttpRestHelper.parseBigFileInfoFromServer((String) dfs.getReturnValue());

                                // 服服务返回的查询结果码（详见http文档中“【接口1015-23-7】”的详细说明）：
                                // * 0 表示该文件不存在(未被上传过)
                                // * 1 表示该文件已经存在且已上传完成（无需再次上传）
                                // * 2 表示该文件已经存在查未上传完成（此时chunkCountInServer才有意义）
                                String retCode = ret.getString("retCode");
                                // 该文件在服务端已传完的分块个数（为>=0的整数）
                                String chunkCountInServer = ret.getString("chunkCount");

                                Log.w(TAG, "【短视频上传】从服务端查询该文件的断点续传信息成功返回，数据结果：retCode=" + retCode + ", chunkCountInServer="
                                        + chunkCountInServer + "（videoFilePath=" + videoFilePath + ", videoFileName=" + videoFileName + ", videoFileMd5=" + videoFileMd5 + "）");

                                // 该md5码对应的文件已经存在于服务器上了（不需要重复上传）
                                if ("1".equals(retCode))
                                {
                                    Log.i(TAG, "【短视频上传】大文件：" + videoFilePath + "已经存在于服务器，本次不需要重复上传文件了！【END】");
                                    utl.onUploadSuccess(videoFileName, videoFileMd5, videoFilePath, -1, -1);
                                    return;
                                }
                                // 表示该文件已经存在查未上传完成（此时chunkCountInServer才有意义）
                                else if ("2".equals(retCode))
                                {
                                    // 比如“chunkCountInServer”=2时，表示当前服务端已经上传了2块，但因为无法确定最后一块
                                    // 是否已被正常上传完成，所以本次的上传应该重传这最后一块，即本次应从“第2块”开始传
                                    startChunck = CommonUtils.getIntValue(chunkCountInServer, 1);

                                    Log.i(TAG, "【短视频上传】大文件：" + videoFilePath + "的第"+chunkCountInServer
                                            +"块已经上传，本次将从第"+startChunck+"块续传。。。");
                                }
                                // 表示该文件不存在(未被上传过)
                                else
                                {
                                    Log.d(TAG, "【短视频上传】大文件：" + videoFilePath + "从未上传过，本次将从第"+startChunck+"块开始从头上传。。。。。。");
                                }
                            } else
                            {
                                Log.w(TAG, "【短视频上传】从服务端查询该文件的断点续传信息成功返回了，但返回结果为空，" + "，本次任务没有继续！（videoFilePath="
                                        + videoFilePath + ", videoFileName=" + videoFileName + ", videoFileMd5=" + videoFileMd5 + "）");
                                utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
                                return;
                            }
                        }

                        //** 检查完成，开始决定文件数据的上传了
                        if (um != null)
                        {
                            Log.i(TAG, "【短视频上传】要上传的大文件路径是：" + videoFilePath + "， 上传马上开始.......");

                            // 新建一个上传任务
                            BigFileUploadTask.Builder ub = new BigFileUploadTask.Builder();
                            ub.setFileName(videoFileName);
                            ub.setId(videoFileMd5);
                            ub.setUrl(Const.SHORTVIDEO_UPLOADER_CONTROLLER_URL_ROOT);
                            ub.setFilePath(videoFilePath);
                            ub.setChunck(startChunck);// 1 is base start chunck
                            ub.setFileMd5(videoFileMd5);

                            // 为任务添加监听器
                            ub.setListener(utl);

                            // 加入任务并由开始线程调度和执行
                            um.addUploadTask(ub.build());
                        }
                        else
                        {
                            Log.w(TAG, "【短视频上传】UploadManager不能是null，本次上传取消!");
                            utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
                            return;
                        }
                    }
                }.execute();

                return;
            }
            else
            {
                Log.w(TAG, "【短视频上传】上传短视频时，localUser不应为null，本次上传取消!");
                utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
                return;
            }
        }
        else
        {
            utl.onError(videoFileName, videoFileMd5, videoFilePath, -1, -1, -1);
            WidgetUtils.showWithDialog(context, "提示", "短视频上传失败，请重启APP后再试！");
            return;
        }
    }

    /**
     * 短视频上传状态监听器实现类。
     *
     * @since 4.4
     */
    private static abstract class ShortVideoUploadTaskListenerForChat implements BigFileUploadTaskListener
    {
        protected Context context = null;
        protected Message entityInChatListView = null;


        public ShortVideoUploadTaskListenerForChat(Context context, Message entityInChatListView)
        {
            this.context = context;
            this.entityInChatListView = entityInChatListView;
        }

        @Override
        public void onUploading(String fileName, String fileMd5, String fileFullPath
                , int percent, int chunck, int chuncks)
        {
            Log.d(TAG, "【短视频上传-onUploading-["+(chunck-1)+"/"+chuncks+"]-%"+percent+"】"
                    + fileName + ",上传进度：" + percent);

            // 更新本次上传任务所对应的聊天列表中的数据单元对象
            entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processing);
            entityInChatListView.setSendStatusSecondaryProgress(percent);// 0~100整数

            // 通知观察者，尝试刷新UI界面上的上传进度显示
            notificateStatusChangedObserver(fileName, fileMd5, fileFullPath);
        }

        @Override
        public void onUploadSuccess(String fileName, String fileMd5, String fileFullPath, int chunck, int chuncks)
        {
            Log.i(TAG, "【短视频上传-onUploadSuccess】" + fileName + ", chunk/chunks=" + (chunck - 1) + "/" + chuncks);

            //**  更新文件上传任务所对应的聊天列表中的数据单元对象
            // 此处还不能设置为processOk，因为接下来还要上传视频首帧预览图片
//            entityInChatListView.setSendStatusSecondary(ChatMsgEntity.SendStatusSecondary.processOk);
            entityInChatListView.setSendStatusSecondaryProgress(100);// 0~100整数

            // 通知观察者，尝试刷新UI界面上的上传进度显示
            notificateStatusChangedObserver(fileName, fileMd5, fileFullPath);

            // 上传任务成功完成后的通知（包括继续上传视频首帧预览图片）
            notificateFileUploaedSucessObserver();
        }

        @Override
        public void onError(String fileName, String fileMd5, String fileFullPath
                , int errorCode, int chunck, int chuncks)
        {
            Log.w(TAG, "【短视频上传-onError】errorCode=" + errorCode + ",file=" + fileName + ", chunk/chunks=" + (chunck - 1) + "/" + chuncks);

            // 更新本次上传任务所对应的聊天列表中的数据单元对象
            entityInChatListView.setSendStatusSecondary(Message.SendStatusSecondary.processFaild);
            // 直接像微信一样标识整个消息的发送状态为失败
            entityInChatListView.setSendStatus(Message.SendStatus.sendFaild);

            // 通知观察者，尝试刷新UI界面上的上传进度显示
            notificateStatusChangedObserver(fileName, fileMd5, fileFullPath);
        }

        @Override
        public void onPause(String fileName, String fileMd5, String fileFullPath, int chunck, int chuncks)
        {
            Log.i(TAG, "【短视频上传-onPause】" + fileName + ", chunk/chunks=" + (chunck - 1) + "/" + chuncks);
        }

        protected void notificateStatusChangedObserver(String fileName, String fileMd5, String fileFullPath)
        {
            // 通知观察者，尝试刷新UI界面上的上传进度显示
            Observer fileStatusChangedObserver = MyApplication.getInstance(this.context)
                    .getIMClientManager().getBigFileUploadManager().getFileStatusChangedObserver();
            if(fileStatusChangedObserver != null)
                fileStatusChangedObserver.update(null, new Object[]{fileName, fileMd5, fileFullPath});
        }

        /**
         * 上传任务成功完成后的通知。
         */
        protected abstract void notificateFileUploaedSucessObserver();
    }
}
